package com.inetools.gwt.gajaxs.client;

import java.util.ArrayList;
import java.util.LinkedHashSet;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.search.client.Result;
import com.google.gwt.search.client.ResultSetSize;
import com.google.gwt.search.client.Search;
import com.google.gwt.search.client.SearchCompleteHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Widget;


// TODO Make this work with general searcher
public class SearchResultsController  
implements SearchCompleteHandler, SearchResultsControllerIntercepter, SearchResultViewerStateListener 
{

	SearchResultsControllerMessages searchResultsControllerMessages = 
		GWT.create( SearchResultsControllerMessages.class);

	// For error listener -------------------------------------------------------------------------
	public interface SearchResultsControllerErrorListener {
		public void onSearchCompleteError( String errorMsg, Throwable throwable);
	}
	protected LinkedHashSet<SearchResultsControllerErrorListener> searchResultsControllerErrorListenerLinkedHashSet = 
		new LinkedHashSet<SearchResultsControllerErrorListener>();
	public boolean addSearchResultsControllerErrorListener( SearchResultsControllerErrorListener searchResultsControllerErrorListener) {
		if ( searchResultsControllerErrorListener == null) return false;
		return searchResultsControllerErrorListenerLinkedHashSet.add( searchResultsControllerErrorListener);
	}
	public boolean removeSearchResultsControllerErrorListener( SearchResultsControllerErrorListener searchResultsControllerErrorListener) {
		return searchResultsControllerErrorListenerLinkedHashSet.remove( searchResultsControllerErrorListener);
	}
	protected void notifySearchResultsControllerError( String errorMsg, Throwable throwable) {
		for( SearchResultsControllerErrorListener searchResultsControllerErrorListener : searchResultsControllerErrorListenerLinkedHashSet) {
			searchResultsControllerErrorListener.onSearchCompleteError( errorMsg, throwable);
		} // for
	}
	// --------------------------------------------------------------------------------------------
	
	protected Search searchObj = null;
	
	protected int unitResultsNum = 1;
	static public final int DEFAULT_REFRESH_INTERVAL = 2500;
	protected int refreshInterval = DEFAULT_REFRESH_INTERVAL;
	/**
	 * 
	 * @param searchObj
	 * @param unitResultsNum
	 * @param refreshInterval: When this is -1, disable timer feature of refreshing with new Results 
	 */
	public SearchResultsController( Search searchObj, int unitResultsNum, int refreshInterval) {
		this.searchObj = searchObj;

		this.unitResultsNum = unitResultsNum;
		if ( this.unitResultsNum < 1) this.unitResultsNum = 1;
GWT.log( "this.unitResultsNum = " + this.unitResultsNum, null);
		this.refreshInterval = refreshInterval;
		if ( ( refreshInterval > 0) && ( refreshInterval < DEFAULT_REFRESH_INTERVAL)) 
			this.refreshInterval = DEFAULT_REFRESH_INTERVAL;
	}
	
	// For SearchControllerListener listener ------------------------------------------------------
	protected LinkedHashSet<Result> searchResultStackLinkedHashSet = new LinkedHashSet<Result>();	

	protected LinkedHashSet<SearchResultsControllerListener> searchControllerListenerLinkedHashSet = 
		new LinkedHashSet<SearchResultsControllerListener>();
	public boolean addSearchControllerListener( SearchResultsControllerListener searchControllerListener) {
		if ( searchControllerListener == null) return false;
		return searchControllerListenerLinkedHashSet.add( searchControllerListener);
	}
	public boolean removeSearchControllerListener( SearchResultsControllerListener searchControllerListener) {
		return searchControllerListenerLinkedHashSet.remove( searchControllerListener);
	}
	
	private int fireSearchResultStackFilledEventCounter = 0; 	// Used to avoid duplicate rendering 
	protected void fireSearchResultStackFilledEvent( LinkedHashSet<Result> searchResultStackLinkedHashSet) {
		++fireSearchResultStackFilledEventCounter;
		for( SearchResultsControllerListener searchControllerListener : searchControllerListenerLinkedHashSet) {
			searchControllerListener.onSearchResultStackFilled( 
					searchObj, searchResultStackLinkedHashSet, attributionWidgetLinkedHashSet);
		} // for
	}
	protected void fireSearchResultStackClearedEvent() {
		for( SearchResultsControllerListener searchResultStackListener : searchControllerListenerLinkedHashSet) {
			searchResultStackListener.onSearchResultStackCleared();
		} // for
	}
	
	protected void clearSearchResultStack() {
		searchResultStackLinkedHashSet.clear();
		attributionWidgetLinkedHashSet.clear();
		fireSearchResultStackClearedEvent();
	}
	// --------------------------------------------------------------------------------------------

	// Monitor of viewer status  -------------------------------------------------------------------
	protected boolean viewerReadyFlag = true;
	// Invoked by viewer
	@Override
	public void viewerBusy() {
		viewerReadyFlag = false;
	}
	// Invoked by viewer
	@Override
	public void viewerReady() {
		viewerReadyFlag = true;
	}
	// --------------------------------------------------------------------------------------------

	private class AddSearchCompleteHandlerIssueWorkArounder implements Cloneable {
		// To track times of calling onSearchComplete by DeferredCommand to avoid accumulation in DeferredCommand queue
		private int delivertiveCallCounter = 0;
		public int getDelivertiveCallCounter() {
			return delivertiveCallCounter;
		}
		public void incrementDelivertiveCallCounter() {
			++delivertiveCallCounter;
		}

		private int eventHash = -1;
		protected int getEventHash() {
			return eventHash;
		}
		protected void setEventHash(int eventHash) {
			this.eventHash = eventHash;
		}
		
		private boolean notEnoughResultFlag = false;
		public boolean isNotEnoughResultFlag() {
			return notEnoughResultFlag;
		}
		public void setNotEnoughResultFlag( boolean notEnoughResultFlag) {
			this.notEnoughResultFlag = notEnoughResultFlag;
		}
		
		private void reset( int eventHash) {
			delivertiveCallCounter = 0;
			this.eventHash = eventHash;
			notEnoughResultFlag = false;
		}
		private void reset() {
			delivertiveCallCounter = 0;
			eventHash = -1;
			notEnoughResultFlag = false;
		}
		
		public AddSearchCompleteHandlerIssueWorkArounder() {
		}
		
		// Object.clone method is not available in GWT. For issue detail, refer to following: 
		// Issue 1843: Implement Object.clone()
		// http://code.google.com/p/google-web-toolkit/issues/detail?id=1843
		// This constructor is for workaround that issue.
		
		public AddSearchCompleteHandlerIssueWorkArounder( AddSearchCompleteHandlerIssueWorkArounder clonee) {
			this.eventHash = clonee.getEventHash();
			this.delivertiveCallCounter = clonee.getDelivertiveCallCounter();
			this.notEnoughResultFlag = clonee.isNotEnoughResultFlag();
		}
		
	} // private class AddSearchCompleteHandlerIssueWorkArounder
	AddSearchCompleteHandlerIssueWorkArounder addSearchCompleteHandlerIssueWorkArounder = 
		new AddSearchCompleteHandlerIssueWorkArounder();
	AddSearchCompleteHandlerIssueWorkArounder addSearchCompleteHandlerIssueWorkArounderCopy = null;
	
	// Timer related ------------------------------------------------------------------------------
// Maybe saving pageIndex and timerSelfCallbackFlag as set to queue object can be the key to solve the difficulties	
	private boolean timerSelfCallbackFlag = false;
	private int pageIndexCopy = 0;
	private int searchResultStackLinkedHashSetSizeCopy = 0;
	/**
	 * This will be invoked in following 2 cases
	 * 	Added unique Result objects to searchResultStackLinkedHashSet for unitResultsNum times
	 * 	There weren't enough Result objects and it reached the very last Result object
	 */
	protected void update() {
		// Cancel timer to avoid called back while processing
		//this.cancel();
		timerDriver.cancel();
		
		// Check if viewer is not in busy state
		if ( !viewerReadyFlag) {
			//this.schedule( refreshInterval);
			timerDriver.scheduleRun( refreshInterval);
			return;
		}

		// Since searchObj is passed from external component out of this class, it's always better to check
		if ( searchObj == null) return;

GWT.log( "keywords in run method = " + keywords, null);
		
		// For this timer routine, onSearchCompleteIntentionalCaller has nothing to do. So, initialize it.
		onSearchCompleteIntentionalCaller.clearSearchCompleteEventQueue();

GWT.log( "addSearchCompleteHandlerIssueWorkArounderCopy.isNotEnoughResultFlag() = " + addSearchCompleteHandlerIssueWorkArounderCopy.isNotEnoughResultFlag(), null);
		if ( addSearchCompleteHandlerIssueWorkArounderCopy.isNotEnoughResultFlag()) {
			// Start over
			pageIndex = 0;
			clearSearchResultStack();
			addSearchCompleteHandlerIssueWorkArounder = new AddSearchCompleteHandlerIssueWorkArounder();
			fireSearchResultStackFilledEventCounter = 0;
			
			searchObj.addSearchCompleteHandler( this);
			searchObj.execute( keywords);
		}
		else {
GWT.log( "timerSelfCallbackFlag = " + timerSelfCallbackFlag, null);
			if ( timerSelfCallbackFlag) {
				timerSelfCallbackFlag = false;
GWT.log( "searchResultStackLinkedHashSetSizeCopy = " + searchResultStackLinkedHashSetSizeCopy, null);
GWT.log( "searchResultStackLinkedHashSet.size() = " + searchResultStackLinkedHashSet.size(), null);
				if ( searchResultStackLinkedHashSetSizeCopy == searchResultStackLinkedHashSet.size()) {
					addSearchCompleteHandlerIssueWorkArounderCopy.setNotEnoughResultFlag( true);
					pageIndex = 0;
					pageIndexCopy = pageIndex;
GWT.log( "pageIndex = " + pageIndex, null);
					//searchObj.gotoPage( pageIndex);
					if ( searchResultStackLinkedHashSet.size() > 0) {
						fireSearchResultStackFilledEvent( searchResultStackLinkedHashSet);
					}
				}
				//this.schedule( refreshInterval);
				timerDriver.scheduleRun( refreshInterval);
			}
			else {
				clearSearchResultStack();
				JsArray<Result> resultJsArray = (JsArray<Result>)searchObj.getResults();
				boolean isAttributionAdded = false;
				for( ++resultIndex; resultIndex < resultJsArray.length(); resultIndex++) {
					Result result = resultJsArray.get( resultIndex);
					if ( searchResultStackLinkedHashSet.add( result)) {
						// grab attribution if it's not null.
						if ( !isAttributionAdded) {
							isAttributionAdded = true;
							try {
// Once in awhile, it stacks here and FireBug claims that searchObj.getAttribution() is null
// I cannot figure out why it stacks here and what FireBug says means.
								Widget googleAttributionWidget = searchObj.getAttribution();
								if ( googleAttributionWidget != null) {
									attributionWidgetLinkedHashSet.add( googleAttributionWidget);
								}
							}
							catch( JavaScriptException javaScriptException) { // Do nothing
							}
						}
GWT.log( "searchResultStackLinkedHashSet.size() = " + searchResultStackLinkedHashSet.size(), null);
						if ( searchResultStackLinkedHashSet.size() == unitResultsNum) {
							break; // for
						}
					}
				} // for
				if ( searchResultStackLinkedHashSet.size() >= unitResultsNum) {
fireSearchResultStackFilledEventCounter = 0;
					fireSearchResultStackFilledEvent( searchResultStackLinkedHashSet);
					//this.schedule( refreshInterval);
					timerDriver.scheduleRun( refreshInterval);
				}
				else {
					addSearchCompleteHandlerIssueWorkArounder = new AddSearchCompleteHandlerIssueWorkArounder();
					fireSearchResultStackFilledEventCounter = 0;
					
					searchObj.addSearchCompleteHandler( this);
		
//					pageIndexCopy = ++pageIndex;
//GWT.log( "pageIndex = " + pageIndex, null);
//					searchObj.gotoPage( pageIndex);
					searchResultStackLinkedHashSetSizeCopy = searchResultStackLinkedHashSet.size();
GWT.log( "pageIndex = " + ( pageIndex + 1), null);
					searchObj.gotoPage( ++pageIndex);
					
					// Run immediately here, instead of scheduling.
					// No, have to wait for searchObj.gotoPage( ++pageIndex) complete.
					timerSelfCallbackFlag = true;

					if ( refreshInterval > 0) {
						//this.schedule( refreshInterval);
						timerDriver.scheduleRun( refreshInterval);
					}
					else {
						//DeferredCommand.addCommand did not work. It processed before searchObj.gotoPage finished.
						/*
						DeferredCommand.addCommand( new Command() {
							@Override
							public void execute() {
								update();							
							}
						});
						*/
						timerDriver.scheduleRun( 250);
					}
				}
			}
		}
	} // public void run() 
	
	/**
	 * Made this dummy timer class since Google's timer class preserve the context, and  
	 * that is problem for new query by changing keywords, because it uses the previous 
	 * keywords (not new keywords) had set in the context.
	 * @author ay
	 */
	protected class TimerDriver extends Timer {
		private int scheduleCounter = 0;
		private int executionCounter = 0;

		/**
		 * schedule cancels previous schedule call. 
		 * For detail, check the Timer class source code at http://www.google.com/codesearch/p?hl=en&sa=N&cd=1&ct=rc#A1edwVHBClQ/user/src/com/google/gwt/user/client/Timer.java&q=cancel%20package:http://google-web-toolkit\.googlecode\.com
		 * @param delayMillis
		 */
		public void scheduleRun( int delayMillis) {
			if ( delayMillis < 1) return;
			++scheduleCounter;
GWT.log( "Timer scheduled " + scheduleCounter + " times. Timer executed " + executionCounter + " times.", null);
			this.schedule( delayMillis);
		}
		
		@Override
		public void run() {
			executionCounter++;
GWT.log( "Timer scheduled " + scheduleCounter + " times. Timer executed " + executionCounter + " times.", null);
			update();			
		}
	}
	private TimerDriver timerDriver = new TimerDriver();
	public void searchResultsControllerTimerInitialize() {
		timerDriver.cancel();
		timerDriver = new TimerDriver();
	}
	
	public void searchResultsControllerTimerCancel() {
		//this.cancel();
		timerDriver.cancel();
	}
	public void searchResultsControllerTimerResume() {
		//this.schedule( refreshInterval);
		timerDriver.scheduleRun( refreshInterval);
	}
	// --------------------------------------------------------------------------------------------

	protected int pageIndex = 0;
	protected int resultIndex = 0;
	
	private class OnSearchCompleteIntentionalCaller implements Command {
		private ArrayList<SearchCompleteEvent> searchCompleteEventArrayList = new ArrayList<SearchCompleteEvent>();
		public void clearSearchCompleteEventQueue() {
			searchCompleteEventArrayList.clear();
		}
		public void addSearchCompleteEvent( SearchCompleteEvent searchCompleteEvent) {
			if ( searchCompleteEvent == null) return;
			searchCompleteEventArrayList.add( searchCompleteEvent);
		}
/*		
		private SearchCompleteEvent event;
		public OnSearchCompleteIntentionalCaller() {
			this.event = event;
		}

		@Override
		public void execute() {
			onSearchComplete( event);
		}
*/
		public OnSearchCompleteIntentionalCaller() {
		}
		
		@Override
		synchronized public void execute() {
			int searchCompleteEventIndex = searchCompleteEventArrayList.size() - 1;
GWT.log( "searchCompleteEventIndex = " + searchCompleteEventIndex, null);
			if ( searchCompleteEventIndex < 0) return;
			SearchCompleteEvent searchCompleteEvent = 
				searchCompleteEventArrayList.get( searchCompleteEventIndex);
			searchCompleteEventArrayList.remove( searchCompleteEventIndex);
			onSearchComplete( searchCompleteEvent);
		}
	} // private class OnSearchCompleteIntentionalCaller implements Command
	protected OnSearchCompleteIntentionalCaller onSearchCompleteIntentionalCaller = 
		new OnSearchCompleteIntentionalCaller();
	
	protected LinkedHashSet<Widget> attributionWidgetLinkedHashSet = new LinkedHashSet<Widget>();
	
	@Override
	public void onSearchComplete(SearchCompleteEvent event) {
		//this.cancel();
		timerDriver.cancel();

GWT.log( "event.hashCode() = " + event.hashCode(), null);
		searchObj = event.getSearch();
GWT.log( "pageIndex = " + pageIndex, null);
		
		// Search in general has bug on onSearchComplete. 
		// Unless I remove it onSearchComplete handler, it keeps calling back
		// However, if I remove onSearchComplete handler, it won't get next search result set

		JsArray<Result> resultJsArray = (JsArray<Result>)searchObj.getResults();
		if ( resultJsArray == null) {
//TODO Need to confirm requesting more than available. 
//TODO Need to think of timer cancellation and addSearchCompleteHandlerIssueWorkArounder initialization
GWT.log( "resultJsArray is null", null);
			searchObj.removeSearchCompleteHandler( this);
			if ( searchResultStackLinkedHashSet.size() > 0) {
GWT.log( "searchResultStackLinkedHashSet.size() = " + searchResultStackLinkedHashSet.size(), null);
				fireSearchResultStackFilledEvent( searchResultStackLinkedHashSet);
			}
			return;
		}
		if ( resultJsArray.length() < 1) {
//TODO Need to confirm requesting more than available. 
GWT.log( "resultJsArray is empty", null);
			searchObj.removeSearchCompleteHandler( this);
			if ( searchResultStackLinkedHashSet.size() > 0) {
GWT.log( "searchResultStackLinkedHashSet.size() = " + searchResultStackLinkedHashSet.size(), null);
				fireSearchResultStackFilledEvent( searchResultStackLinkedHashSet);
			}
			return;
		}		
GWT.log( "resultJsArray.length() = " + resultJsArray.length(), null);



//I cannot rely on searchObj.getCursor method, since Cursor is optional feature 
GWT.log( "searchObj.getCursor() = " + searchObj.getCursor(), null);
if ( searchObj.getCursor() != null) {
	GWT.log( "searchObj.getCursor().getCurrentPageIndex() = " + searchObj.getCursor().getCurrentPageIndex(), null);
	GWT.log( "searchObj.getCursor().getEstimatedResultCount() = " + searchObj.getCursor().getEstimatedResultCount(), null);
}

		// Work around for addSearchCompleteHandler method issue of Search class ------------------
		if ( event.hashCode() == addSearchCompleteHandlerIssueWorkArounder.getEventHash()) {
			if ( fireSearchResultStackFilledEventCounter < 1) {
				searchObj.removeSearchCompleteHandler( this);
				onSearchCompleteIntentionalCaller.clearSearchCompleteEventQueue();
				addSearchCompleteHandlerIssueWorkArounderCopy = 
					new AddSearchCompleteHandlerIssueWorkArounder( addSearchCompleteHandlerIssueWorkArounder);
				addSearchCompleteHandlerIssueWorkArounderCopy.setNotEnoughResultFlag( true);
				addSearchCompleteHandlerIssueWorkArounder.reset();
				fireSearchResultStackFilledEvent( searchResultStackLinkedHashSet);

				// start timer. Watch out! There is the case: after starting timer, onSearchComplete is called back couple times
			}
			//this.schedule( refreshInterval);
			timerDriver.scheduleRun( refreshInterval);
		}
		else {
			addSearchCompleteHandlerIssueWorkArounder.setEventHash( event.hashCode());
			int searchResultStackPreSize = searchResultStackLinkedHashSet.size();
			if ( searchResultStackLinkedHashSet.size() < unitResultsNum) {
				boolean isAttributionAdded = false;
				for( resultIndex = 0; resultIndex < resultJsArray.length(); resultIndex++) {
					Result result = resultJsArray.get( resultIndex);
					if ( searchResultStackLinkedHashSet.add( result)) {
GWT.log( "searchResultStackLinkedHashSet.size() = " + searchResultStackLinkedHashSet.size(), null);
						// grab attribution if it's not null.
						if ( !isAttributionAdded) {
							isAttributionAdded = true;
							try {
								Widget googleAttributionWidget = searchObj.getAttribution();
								if ( googleAttributionWidget != null) {
									attributionWidgetLinkedHashSet.add( googleAttributionWidget);
								}
							}
							catch( JavaScriptException javaScriptException) { // Do nothing
							}
						}
						if ( searchResultStackLinkedHashSet.size() == unitResultsNum) {
							break; // for
						}
					}
				} // for
			}
			
			if ( searchResultStackLinkedHashSet.size() >= unitResultsNum) {
				if ( fireSearchResultStackFilledEventCounter < 1) {
					searchObj.removeSearchCompleteHandler( this);
					onSearchCompleteIntentionalCaller.clearSearchCompleteEventQueue();
					addSearchCompleteHandlerIssueWorkArounderCopy = 
						new AddSearchCompleteHandlerIssueWorkArounder( addSearchCompleteHandlerIssueWorkArounder);
					addSearchCompleteHandlerIssueWorkArounderCopy.setNotEnoughResultFlag( false);
					addSearchCompleteHandlerIssueWorkArounder.reset(); 	// Watch out if onSearchComplete called back later
GWT.log( "Got the enough Result objects. Watch out for the call back to onSearchComplete later than this", null);
					fireSearchResultStackFilledEvent( searchResultStackLinkedHashSet);

					// start timer. Watch out! There is the case: after starting timer, onSearchComplete is called back couple times
				}
				//this.schedule( refreshInterval);
				timerDriver.scheduleRun( refreshInterval);
			}
			else { // Have not filled requested number of Result object yet
				if ( searchResultStackPreSize < searchResultStackLinkedHashSet.size())
				{ // Search returned new valid Result objects
					addSearchCompleteHandlerIssueWorkArounder.reset( event.hashCode());
					searchObj.gotoPage( ++pageIndex); 
					
//					DeferredCommand.addCommand( new OnSearchCompleteIntentionalCaller( event));
					onSearchCompleteIntentionalCaller.addSearchCompleteEvent( event);
					DeferredCommand.addCommand( onSearchCompleteIntentionalCaller);
				}
				else {
GWT.log( "addSearchCompleteHandlerIssueWorkArounder.getDelivertiveCallCounter() = " + addSearchCompleteHandlerIssueWorkArounder.getDelivertiveCallCounter(), null);
					addSearchCompleteHandlerIssueWorkArounder.incrementDelivertiveCallCounter();
//					DeferredCommand.addCommand( new OnSearchCompleteIntentionalCaller( event));
					onSearchCompleteIntentionalCaller.addSearchCompleteEvent( event);
					DeferredCommand.addCommand( onSearchCompleteIntentionalCaller);
				}
			}
		}
		// ----------------------------------------------------------------------------------------

	} // public void onSearchComplete(SearchCompleteEvent event)

	protected String keywords = ""; 	// Will be referred in timer's run method

	protected class SearchExecutionCommand implements Command {
		protected SearchCompleteHandler searchCompleteHandler;
		public SearchExecutionCommand( SearchCompleteHandler searchCompleteHandler) {
			this.searchCompleteHandler = searchCompleteHandler;
		}
		
		@Override
		public void execute() {
			//Reset searchObj
			searchObj.removeSearchCompleteHandler( searchCompleteHandler);
			
			// Cancel timer
			//this.cancel();
			//searchResultsControllerTimerInitialize();
			searchResultsControllerTimerCancel();
			
			searchObj.clearResults();
			
			pageIndex = 0;
			// searchResultStackLinkedHashSet.clear();
			clearSearchResultStack();
			addSearchCompleteHandlerIssueWorkArounder = new AddSearchCompleteHandlerIssueWorkArounder();
			fireSearchResultStackFilledEventCounter = 0;
			onSearchCompleteIntentionalCaller.clearSearchCompleteEventQueue();
			attributionWidgetLinkedHashSet.clear();
			
			searchObj.setResultSetSize( ResultSetSize.LARGE);
			searchObj.addSearchCompleteHandler( searchCompleteHandler);
			searchObj.execute( keywords);
		}
	}
	protected SearchExecutionCommand searchExecutionCommand = new SearchExecutionCommand( this);
	
	public void executeSearch( String searchKeywords) 
	throws NullPointerException, IllegalArgumentException {
		if ( searchKeywords == null) {
			throw new IllegalArgumentException( searchResultsControllerMessages.nullKeyword());
		}
		else if ( searchKeywords.length() < 1) {
			throw new IllegalArgumentException( searchResultsControllerMessages.emptyKeyword());
		}
		
		keywords = searchKeywords;
		
		if ( searchObj == null) {
			throw new NullPointerException( searchResultsControllerMessages.nullSearchObj());
		}
		
/*		
		//Reset searchObj
		searchObj.removeSearchCompleteHandler( this);
		//this.cancel();
		searchResultsControllerTimerInitialize();
		searchObj.clearResults();
		
		pageIndex = 0;
		searchResultStackLinkedHashSet.clear();
		addSearchCompleteHandlerIssueWorkArounder = new AddSearchCompleteHandlerIssueWorkArounder();
		fireSearchResultStackFilledEventCounter = 0;
		onSearchCompleteIntentionalCaller.clearSearchCompleteEventQueue();
		
		searchObj.setResultSetSize( ResultSetSize.LARGE);
		searchObj.addSearchCompleteHandler( this);
		searchObj.execute( keywords);
*/
		DeferredCommand.addCommand( searchExecutionCommand);
		
	} // public void executeSearch( String keywords)
	
	
	
	public void quit() {
		DeferredCommand.addCommand( 
				new Command() {
					@Override
					public void execute() {
						//Reset searchObj
						searchObj.removeSearchCompleteHandler( SearchResultsController.this);
						
						// Cancel timer
						//this.cancel();
						//searchResultsControllerTimerInitialize();
						searchResultsControllerTimerCancel();
					}
				}
		);
	} // public void quit()
}
